#!/usr/bin/env bash
# Copyright (c) 2021 maminjie <maminjie2@huawei.com>
# SPDX-License-Identifier: MulanPSL-2.0

method_def abi

usage_abi() {
printf "abi: Analyze the Application Binary Interfaces (ABI)

usage:
    ${PROG} abi subcmd [options] <args...>

subcmd:
$(__abi_usage_commands)

notes:
    Type '${PROG} abi help <subcmd>' for help on a specific subcommand.

"
}

do_abi() {
    local subcmd=$(__abi_get_subcmd "$1")
    if [ -z "$subcmd" ]; then
        usage_abi; exit
    else
        shift; abi_do_$subcmd "$@"
    fi
}

# __abi_get_subcmd cmd
#   Get the fullname of cmd
# Returns:
#   "" or fullname
__abi_get_subcmd() {
    local cmd=""
    case "${1}" in
        "h"|"help")
            cmd="help"
            ;;
        "so"|"sodiff")
            cmd="sodiff"
            ;;
        "pkg"|"pkgdiff")
            cmd="pkgdiff"
            ;;
        "bin"|"bincheck")
            cmd="bincheck"
            ;;
        *)
            ;;
    esac
    echo "$cmd"
}

__abi_usage_commands() {
    local usages=$(declare_functions | grep -E "^abi_usage_[a-zA-Z0-9_]+")
    for usage in $usages; do
        eval local usageinfo=\"$(${usage} | head -n 1)\"
        printf "    %s\n" "$usageinfo"
    done | format_column ':'
}

abi_usage_help() {
printf "help (h): Show the specific subcommand usage

usage:
    ${PROG} abi help subcmd

"
}

# abi_do_help subcmd
abi_do_help() {
    if [ $# -ne 1 ]; then
        abi_usage_help; exit
    fi
    local subcmd=$(__abi_get_subcmd "$1")
    local usage="abi_usage_$subcmd"
    if [ "$(type -t "$usage")" = "function" ] ; then
        $usage
    else
        echo "Unknown command '$1'"
    fi
}

abi_usage_sodiff() {
printf "sodiff (so): Compares the ABI of two shared libraries in ELF format

usage:
    ${PROG} abi so [options] [<file1> <file2>]

powered by abidiff

$(abidiff -h 2>/dev/null | sed '1d' | sed -r -e 's/^\s+//g' -e 's/^-/    -/g')

"
}

abi_do_sodiff() {
    require_command "abidiff"

    local oriargs="$@"

    local ARGS=$(getopt -o ":h" -l ":help" -n "abi" -- "$@")
    eval set -- "${ARGS}"

    while true; do
        case "${1}" in
            -h|--help)
                abi_usage_sodiff; exit
                ;;
            *)
                break
                ;;
        esac
    done

    abidiff $oriargs
}

abi_usage_pkgdiff() {
printf "pkgdiff (pkg): Compares the ABI of the ELF binaries contained in two software packages.

    The software package formats currently supported are Deb, RPM, tar archives (either compressed or not)
and plain directories that contain binaries.

usage:
    ${PROG} abi pkg [options] [<package1> <package2>]
    ${PROG} abi pkg dir1 dir2                          # The directories contain the rpm packages

powered by abipkgdiff

$(abipkgdiff -h 2>/dev/null | sed '1d' | sed -r -e 's/^\s+//g' -e 's/^-/    -/g')

"
}

abi_do_pkgdiff() {
    require_command "abipkgdiff"

    local oriargs="$@"

    local ARGS=$(getopt -o ":h" -l ":help" -n "abi" -- "$@")
    eval set -- "${ARGS}"

    while true; do
        case "${1}" in
            -h|--help)
                abi_usage_pkgdiff; exit
                ;;
            *)
                break
                ;;
        esac
    done

    set $oriargs    # reset params

    if [[ -d "$1" && -d "$2" ]]; then
        __abi_do_pkgs_diff "$1" "$2"
    else
        abipkgdiff $@
    fi
}

# __abi_do_pkgs_diff dir1 dir2
__abi_do_pkgs_diff() {
    local src_rpms=$(ls $1/*.rpm 2> /dev/null)
    local dst_rpms=$(ls $2/*.rpm 2> /dev/null)
    declare -A src_dict
    declare -A dst_dict

    for r in $src_rpms; do
        local name=$(rpm_name "$r")
        if [ -n "$name" ]; then
            dict_add src_dict "$name" "$r"
        fi
    done
    for r in $dst_rpms; do
        local name=$(rpm_name "$r")
        if [ -n "$name" ]; then
            dict_add dst_dict "$name" "$r"
        fi
    done

    local src_keys=$(dict_keys src_dict)
    local i=1
    local dcnt=0
    for k in $src_keys; do
        local srpm=$(dict_get src_dict "$k")
        local drpm=$(dict_get dst_dict "$k")
        if [ -z "$drpm" ]; then
            echo "$i) -only $srpm"
            ((dcnt++))
        else
            local res=$(abipkgdiff "$srpm" "$drpm")
            if [ -z "$res" ]; then
                echo "$i) equal $srpm $drpm"
            else
                echo "$i) diff $srpm $drpm"
                ((dcnt++))
                echo "$res"
            fi
            dict_remove dst_dict "$k"
        fi
        ((i++))
    done
    local dst_values=$(dict_values dst_dict)
    for v in $dst_values; do
        echo "$i) +only $v"
        ((dcnt++))
        ((i++))
    done
    echo ""
    echo "There are about $dcnt differences..."
}

abi_usage_bincheck() {
printf "bincheck (bin): Check the informations of binary commands

usage:
    ${PROG} abi bin [<packages>]

params:
    packages - (optional) the names (string list of list file) of rpms,
               default these names come from the rpms in the current directory

notes:
    The rpm should be installed in system; You can refer to yumpkgs.sh,
    execute this command after update or downgrade packages.

"
}

# abi_do_bincheck [packages]
abi_do_bincheck() {
    local pkgs=""
    if [ -n "$1" ]; then
        pkgs=$(get_list "$1")
    else
        pkgs=$(yumpkg_get_binnames)
    fi
    local i=1
    for pkg in $pkgs; do
        echo "$i) checking package $pkg ..."
        local rawcmds=$(rpm -ql "$pkg" | grep -E "/bin/|/sbin/")
        if [ -z "$rawcmds" ]; then
            continue
        fi
        local cmds=()
        for cmd in $rawcmds; do
            if [ ! -x "$cmd" ]; then
                continue
            fi
            array_add cmds $cmd
        done
        array_uniq cmds
        local j=1
        for cmd in ${cmds[@]}; do
            echo "$i.$j) checking command $cmd ..."
            helpmsg=$(timeout 5 $cmd --help)
            if [ -n "$helpmsg" ]; then
                echo "$helpmsg"
            fi
            ((j++))
        done
        ((i++))
    done
}

abi_docheck() {
    check_command "abidiff"
    check_command "abipkgdiff"
}
